/* Copyright (c) 2004, 2022, Oracle and/or its affiliates.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2.0,
  as published by the Free Software Foundation.

  This program is also distributed with certain software (including
  but not limited to OpenSSL) that is licensed under separate terms,
  as designated in a particular file or component or in included license
  documentation.  The authors of MySQL hereby grant you an additional
  permission to link the program and your derivative works with the
  separately licensed software that they have included with MySQL.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License, version 2.0, for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

/**
  @file ha_sysu.cc

  @brief
  The ha_sysu engine is a stubbed storage engine for sysu purposes only;
  it does nothing at this point. Its purpose is to provide a source
  code illustration of how to begin writing new storage engines; see also
  /storage/sysu/ha_sysu.h.

  @details
  ha_sysu will let you create/open/delete tables, but
  nothing further (for sysu, indexes are not supported nor can data
  be stored in the table). Use this sysu as a template for
  implementing the same functionality in your own storage engine. You
  can enable the sysu storage engine in your build by doing the
  following during your build process:<br> ./configure
  --with-sysu-storage-engine

  Once this is done, MySQL will let you create tables with:<br>
  CREATE TABLE <table name> (...) ENGINE=sysu;

  The sysu storage engine is set up to use table locks. It
  implements an sysu "SHARE" that is inserted into a hash by table
  name. You can use this to store information of state that any
  sysu handler object will be able to see when it is using that
  table.

  Please read the object definition in ha_sysu.h before reading the rest
  of this file.

  @note
  When you create an sysu table, the MySQL Server creates a table .frm
  (format) file in the database directory, using the table name as the file
  name as is customary with MySQL. No other files are created. To get an idea
  of what occurs, here is an sysu select that would do a scan of an entire
  table:

  @code
  ha_sysu::store_lock
  ha_sysu::external_lock
  ha_sysu::info
  ha_sysu::rnd_init
  ha_sysu::extra
  ENUM HA_EXTRA_CACHE        Cache record in HA_rrnd()
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::rnd_next
  ha_sysu::extra
  ENUM HA_EXTRA_NO_CACHE     End caching of records (def)
  ha_sysu::external_lock
  ha_sysu::extra
  ENUM HA_EXTRA_RESET        Reset database to after open
  @endcode

  Here you see that the sysu storage engine has 9 rows called before
  rnd_next signals that it has reached the end of its data. Also note that
  the table in question was already opened; had it not been open, a call to
  ha_sysu::open() would also have been necessary. Calls to
  ha_sysu::extra() are hints as to what will be occuring to the request.

  A Longer sysu can be found called the "Skeleton Engine" which can be 
  found on TangentOrg. It has both an engine and a full build environment
  for building a pluggable storage engine.

  Happy coding!<br>
    -Brian
*/

#include "sql_class.h"           // MYSQL_HANDLERTON_INTERFACE_VERSION
#include "ha_sysu.h"
#include "probes_mysql.h"
#include "sql_plugin.h"
#include "my_sys.h"
#include "sysu_index.h"

static handler *sysu_create_handler(handlerton *hton,
                                    TABLE_SHARE *table,
                                    MEM_ROOT *mem_root);

handlerton *sysu_hton;

/* Interface to mysqld, to check system tables supported by SE */
static const char *sysu_system_database();

static bool sysu_is_supported_system_table(const char *db,
                                           const char *table_name,
                                           bool is_sql_layer_system_table);

/**
 * @authpr:Chenwy
 */
static SYSU_SHARE *get_share(const char *table_name, TABLE *table);

static int free_share(SYSU_SHARE *share);

/* Stuff for shares */
static mysql_mutex_t sysu_mutex; // 锁，对下面的hash表进行上锁

static HASH sysu_open_tables; // 创建一个hash表，看一下目前打开了多少个表

// 给 memory 一个 key 值
static PSI_memory_key sysu_key_memory_sysu_share;
static PSI_memory_info all_sysu_memory[] =
        {
                {&sysu_key_memory_sysu_share, "SYSU_SHARE", PSI_FLAG_GLOBAL},  // 这个是用于管理 share 的 key
                {&sysu_key_memory_index,      "SYSU_INDEX", 0},
                {&sysu_cmp_memory,            "sysu_data",  0}
        };


// 给锁一个key值
static PSI_mutex_key sysu_key_mutex_sysu, sysu_key_mutex_sysu_share_mutex;

// 声明所有的 key 值
static PSI_mutex_info all_sysu_mutexes[] =
        {
                {&sysu_key_mutex_sysu,             "sysu", PSI_FLAG_GLOBAL}, // 这个是用于管理 hash table 的 mutex
                {&sysu_key_mutex_sysu_share_mutex, "sysu_share::mutex", 0} // 这个是管理share里面的mutex
        };

/**
 * @author:Chenwy
 * register :: 这样才能申请内存和key
 */
static void init_sysu_psi_keys(void) {
    const char *category = "sysu";
    int count;

    // 获取里面有多少个数组
    count = array_elements(all_sysu_mutexes);
    // register 记录一下，之后就可以用于上锁
    mysql_mutex_register(category, all_sysu_mutexes, count);

    count = array_elements(all_sysu_memory);
    // register 记录一下，之后就可以用于内存分配
    mysql_memory_register(category, all_sysu_memory, count);
}

/**
 * @author:Chenwy
 * 之后便于后续插入哈希表获取 key 值
 * */
static uchar *sysu_get_key(SYSU_SHARE *share, size_t *length,
                           my_bool not_used MY_ATTRIBUTE((unused))) {
    *length = share->table_name_length;
    return (uchar *) share->table_name;
}

/**
 * @author:Chenwy
 */
static int sysu_init_func(void *p) {
    DBUG_ENTER("sysu_init_func");

    // 初始化这些 key 的值，之后便可以用于分配内存
    init_sysu_psi_keys();

    // 初始化 sysu_mutex, 之后可以用于 get_share 的上锁
    mysql_mutex_init(sysu_key_mutex_sysu, &sysu_mutex, MY_MUTEX_INIT_FAST);

    // 初始化哈希表
    (void) my_hash_init(&sysu_open_tables, system_charset_info, 32, 0, 0,
                        (my_hash_get_key) sysu_get_key, 0, 0,
                        sysu_key_memory_sysu_share);

    sysu_hton = (handlerton *) p;
    sysu_hton->state = SHOW_OPTION_YES;
    sysu_hton->create = sysu_create_handler;
    sysu_hton->flags = HTON_CAN_RECREATE;
    sysu_hton->system_database = sysu_system_database;
    sysu_hton->is_supported_system_table = sysu_is_supported_system_table;

    DBUG_RETURN(0);
}

/**
 * free share ptr
 * @param share
 * @return 0 if success
 * @author HuangDell
 */
static int free_share(SYSU_SHARE *share) {
    DBUG_ENTER("ha_sysu::free_share");
    mysql_mutex_lock(&sysu_mutex);
    if(!--share->user_count)
    {
        // 删除 data_class
        if (share->data_class!=NULL)
            delete share->data_class;
        share->data_class=NULL;

        // 删除 index_class
        if (share->index_class!=NULL)
            delete share->index_class;
        share->index_class = NULL;

        my_hash_delete(&sysu_open_tables,(byte*)share);
        thr_lock_delete(&share->lock);
        mysql_mutex_destroy(&share->mutex);
        my_free(share->table_name);  // TO FIX ?
    }
    mysql_mutex_unlock(&sysu_mutex);
    DBUG_RETURN(0);
}


/**
  @brief
  sysu of simple lock controls. The "share" it creates is a
  structure we will pass to each sysu handler. Do you have to have
  one of these? Well, you have pieces that are used for locking, and
  they are needed to function.
  @author:Chenwy
*/
static SYSU_SHARE *get_share(const char *table_name, TABLE *table) {
    // 这两个用来申请内存
    SYSU_SHARE *tmp_share;
    char *tmp_name;
    uint length; // 表名字的长度

    DBUG_ENTER("ha_sysu::get_share()");

    /**
     * 记住：这个 sysu_mutex 也是需要
     */
    // 上锁，防止冲突
    mysql_mutex_lock(&sysu_mutex);
    // 获取表名称的长度
    length = (uint) strlen(table_name);

    // 如果没有表没有被用户使用，那么就需要创建一个tmp_share
    if (!(tmp_share = (SYSU_SHARE *) my_hash_search(&sysu_open_tables,
                                                    (uchar *) table_name,
                                                    length))) {
        // 一下子对多个指针分配多个地址
        if (!my_multi_malloc(sysu_key_memory_sysu_share,
                             MYF(MY_WME | MY_ZEROFILL),
                             &tmp_share, sizeof(*tmp_share),
                             &tmp_name, length + 1,
                             NullS)) {
            mysql_mutex_unlock(&sysu_mutex);
            DBUG_RETURN(NULL);
        }

        // 新的share
        tmp_share->user_count = 0;
        tmp_share->table_name_length = length;
        tmp_share->table_name = tmp_name;
        strcpy(tmp_share->table_name, table_name);

        // 插入到哈希表中
        if (my_hash_insert(&sysu_open_tables, (uchar *) tmp_share))
            goto error;
        thr_lock_init(&tmp_share->lock);

        // 创建一个新的data_class
        tmp_share->data_class = new Sysu_data();

        // 创建一个新的 index class
        tmp_share->index_class = new Sysu_index();

        // 初始化 tmp_share 里面的mutex
        mysql_mutex_init(sysu_key_mutex_sysu_share_mutex, &tmp_share->mutex, MY_MUTEX_INIT_FAST);

    }
    // tmp_share 的用户数加1
    tmp_share->user_count++;

    // 解开 get_share 锁
    mysql_mutex_unlock(&sysu_mutex);

    DBUG_RETURN(tmp_share);

    error:
    mysql_mutex_unlock(&sysu_mutex);
    free_share(tmp_share);  //
    DBUG_RETURN(NULL);
}


static handler *sysu_create_handler(handlerton *hton,
                                    TABLE_SHARE *table,
                                    MEM_ROOT *mem_root) {
    return new(mem_root) ha_sysu(hton, table);
}

ha_sysu::ha_sysu(handlerton *hton, TABLE_SHARE *table_arg)
        : handler(hton, table_arg) {}


/**
  @brief
  If frm_error() is called then we will use this to determine
  the file extensions that exist for the storage engine. This is also
  used by the default rename_table and delete_table method in
  handler.cc.

  For engines that have two file name extentions (separate meta/index file
  and data file), the order of elements is relevant. First element of engine
  file name extentions array should be meta/index file extention. Second
  element - data file extention. This order is assumed by
  prepare_for_repair() when REPAIR TABLE ... USE_FRM is issued.

  @see
  rename_table method in handler.cc and
  delete_table method in handler.cc
  define file name extensions
  @author HuangDell
*/
#define SDE_EXT ".sde"   // data file name extensions
#define SDI_EXT ".sdi"   // index file name extensions
static const char *ha_sysu_exts[] = {
        SDE_EXT,
        SDI_EXT,
        NullS
};

const char **ha_sysu::bas_ext() const {
    return ha_sysu_exts;
}

/*
  Following handler function provides access to
  system database specific to SE. This interface
  is optional, so every SE need not implement it.
*/
const char *ha_sysu_system_database = NULL;

const char *sysu_system_database() {
    return ha_sysu_system_database;
}

/*
  List of all system tables specific to the SE.
  Array element would look like below,
     { "<database_name>", "<system table name>" },
  The last element MUST be,
     { (const char*)NULL, (const char*)NULL }

  This array is optional, so every SE need not implement it.
*/
static st_handler_tablename ha_sysu_system_tables[] = {
        {(const char *) NULL, (const char *) NULL}
};

/**
  @brief Check if the given db.tablename is a system table for this SE.

  @param db                         Database name to check.
  @param table_name                 table name to check.
  @param is_sql_layer_system_table  if the supplied db.table_name is a SQL
                                    layer system table.

  @return
    @retval TRUE   Given db.table_name is supported system table.
    @retval FALSE  Given db.table_name is not a supported system table.
*/
static bool sysu_is_supported_system_table(const char *db,
                                           const char *table_name,
                                           bool is_sql_layer_system_table) {
    st_handler_tablename *systab;

    // Does this SE support "ALL" SQL layer system tables ?
    if (is_sql_layer_system_table)
        return false;

    // Check if this is SE layer system tables
    systab = ha_sysu_system_tables;
    while (systab && systab->db) {
        if (systab->db == db &&
            strcmp(systab->tablename, table_name) == 0)
            return true;
        systab++;
    }

    return false;
}


/**
  @brief
  Used for opening tables. The name will be the name of the file.

  @details
  A table is opened when it needs to be opened; e.g. when a request comes in
  for a SELECT on the table (tables are not open and closed for each request,
  they are cached).

  Called from handler.cc by handler::ha_open(). The server opens all tables by
  calling ha_open() which then calls the handler specific open().

  @see
  handler::ha_open() in handler.cc
*/

/**
 * 打开一个包含着表数据的文件。找到open()方法，把用来获得share结构一份副本的代码添加到其中，然后打开表
 * @autor:Hlx Chenwy
 */
int ha_sysu::open(const char *name, int mode, uint test_if_locked) {
    DBUG_ENTER("ha_sysu::open");
    char name_buff[FN_REFLEN];
    if (!(share = get_share(name, table)))
        DBUG_RETURN(1);
  /*
  * Call the data class create table method.
  * Note:the fn_format() method correctly creates a file name from the name passed into the method.
  */
  share->data_class->open_table(fn_format(name_buff,name,"",SDE_EXT,
                                                 MY_REPLACE_EXT|MY_UNPACK_FILENAME));
  share->index_class->open_index(fn_format(name_buff,name,"",SDI_EXT,
                                           MY_REPLACE_EXT|MY_UNPACK_FILENAME));
  share->index_class->load_index();
  current_position = 0;
  thr_lock_data_init(&share->lock,&lock,NULL);
  DBUG_RETURN(0);
}


/**
  @brief
  Closes a table.

  @details
  Called from sql_base.cc, sql_select.cc, and table.cc. In sql_select.cc it is
  only used to close up temporary tables or during the process where a
  temporary table is converted over to being a myisam table.

  For sql_base.cc look at close_data_tables().

  @see
  sql_base.cc, sql_select.cc and table.cc

  @author
  Chenwy
*/

int ha_sysu::close(void) {
    DBUG_ENTER("ha_sysu::close");

    share->data_class->close_table();
    share->index_class->save_index();
    share->index_class->destroy_index();
    share->index_class->close_index();
    DBUG_RETURN(0);
}


/**
  @brief
  write_row() inserts a row. No extra() hint is given currently if a bulk load
  is happening. buf() is a byte array of data. You can use the field
  information to extract the data from the native byte array type.

  @details
  sysu of this would be:
  @code
  for (Field **field=table->field ; *field ; field++)
  {
    ...
  }
  @endcode

  See ha_tina.cc for an sysu of extracting all of the data as strings.
  ha_berekly.cc has an sysu of how to store it intact by "packing" it
  for ha_berkeley's own native storage type.

  See the note for update_row() on auto_increments. This case also applies to
  write_row().

  Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
  sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc.

  @see
  item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
  sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc and sql_update.cc

  @author HeLongX HuangDell
*/

int ha_sysu::write_row(uchar *buf) {
    long long pos;
    SDE_INDEX *ndx=new SDE_INDEX();
    ndx->key = NULL;
    ndx->pos = 0;
    ndx->length = 0;
    DBUG_ENTER("ha_sysu::write_row");
    /* record statistic */
    ha_statistic_increment(&SSV::ha_write_count);
    /* add index */
    ndx->length = get_key_len();
    /**
//     I think its wrong
//    memcpy(ndx.key, get_key(), get_key_len());

   **/
    ndx->key = get_key();
    mysql_mutex_lock(&sysu_mutex);
    pos = share->data_class->write_row(buf, table->s->rec_buff_length);
    ndx->pos = pos;
    if (ndx->key)  // 存在键时才插入
        share->index_class->insert_key(ndx, false);
    mysql_mutex_unlock(&sysu_mutex);
    DBUG_RETURN(0);
}


/**
  @brief
  Yes, update_row() does what you expect, it updates a row. old_data will have
  the previous row record in it, while new_data will have the newest data in it.
  Keep in mind that the server can do updates based on ordering if an ORDER BY
  clause was used. Consecutive ordering is not guaranteed.

  @details
  Currently new_data will not have an updated auto_increament record. You can
  do this for sysu by doing:

  @code

  if (table->next_number_field && record == table->record[0])
    update_auto_increment();

  @endcode

  Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc.

  @see
  sql_select.cc, sql_acl.cc, sql_update.cc and sql_insert.cc

  @author HuangDell
*/
int ha_sysu::update_row(const uchar *old_data, uchar *new_data) {

    DBUG_ENTER("ha_sysu::update_row");
    mysql_mutex_lock(&sysu_mutex);
    /* search full file to update */
    share->data_class->update_row((byte *) old_data, new_data,
                                  table->s->rec_buff_length, current_position -
                                                             share->data_class->row_size(table->s->rec_buff_length));
    // if exists key then update key
    if (get_key() != 0) {
        share->index_class->update_key(get_key(), current_position -
                                                  share->data_class->row_size(table->s->rec_buff_length),
                                       get_key_len());
        share->index_class->save_index();
        share->index_class->load_index();
    }
    mysql_mutex_unlock(&sysu_mutex);
    DBUG_RETURN(0);
}


/**
  @brief
  This will delete a row. buf will contain a copy of the row to be deleted.
  The server will call this right after the current row has been called (from
  either a previous rnd_nexT() or index call).

  @details
  If you keep a pointer to the last row or can access a primary key it will
  make doing the deletion quite a bit easier. Keep in mind that the server does
  not guarantee consecutive deletions. ORDER BY clauses can be used.

  Called in sql_acl.cc and sql_udf.cc to manage internal table
  information.  Called in sql_delete.cc, sql_insert.cc, and
  sql_select.cc. In sql_select it is used for removing duplicates
  while in insert it is used for REPLACE calls.

  @see
  sql_acl.cc, sql_udf.cc, sql_delete.cc, sql_insert.cc and sql_select.cc

  @author HuangDell
*/

int ha_sysu::delete_row(const uchar *buf) {
    long long pos;
    DBUG_ENTER("ha_sysu::delete_row");
    if (current_position > 0) {
        /* Let the pointer point to the previous row which is to be deleted */
        pos = current_position - share->data_class->row_size(table->s->rec_buff_length);
    } else
        pos = 0;
    mysql_mutex_lock(&sysu_mutex);
    byte *key = get_key();
    share->data_class->delete_row((byte *) buf,
                                  table->s->rec_buff_length, pos);
    if (key)
        share->index_class->delete_key(key, pos, get_key_len());
    mysql_mutex_unlock(&sysu_mutex);
    DBUG_RETURN(0);
}


/**
  @brief
  Positions an index cursor to the index specified in the handle. Fetches the
  row if available. If the key value is null, begin at the first key of the
  index.
*/

int ha_sysu::index_read_map(uchar *buf, const uchar *key,
                            key_part_map keypart_map MY_ATTRIBUTE((unused)),
                            enum ha_rkey_function find_flag
                            MY_ATTRIBUTE((unused))) {
    DBUG_ENTER("ha_sysu::index_read");
    long long pos;
    pos = share->index_class->get_index_pos((byte *)key, get_key_len());
    if (pos==-1)
        DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
    share->data_class->read_row(buf,table->s->rec_buff_length,pos);
    DBUG_RETURN(0);
}

/*
 这个方法将根据给定的键从索引文件里把相应的记录读入行缓冲区
 @author:JiangZhaohui
*/
int ha_sysu::index_read(byte * buf, const byte * key,
                            uint key_len __attribute__((unused)),
                            enum ha_rkey_function find_flag
                            __attribute__((unused)))
{
    long long pos;

    DBUG_ENTER("ha_sysu::index_read");
    if (key == NULL)
        pos = share->index_class->get_first_pos();
    else
        pos = share->index_class->get_index_pos((byte *) key,key_len);
    if (pos == -1)
        DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
    current_position = pos + share->data_class->row_size(table->s->rec_buff_length);
    share->data_class->read_row(buf,table->s->rec_buff_length, pos);
    share->index_class->get_next_key();
    DBUG_RETURN(0);
}

/*
 与index_read()方法很相似，只不过它是从优化器的其他部分被调用的
 @author:JiangZhaohui
*/
int ha_sysu::index_read_idx(byte * buf, uint index,const byte * key,
                            uint key_len __attribute__((unused)),
                            enum ha_rkey_function find_flag
                                    __attribute__((unused)))
{
    long long pos;
    DBUG_ENTER("ha_sysu::index_read_idx");
    pos = share->index_class->get_index_pos((byte *)key, key_len);
    if (pos==-1)
        DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
    share->data_class->read_row(buf,table->s->rec_buff_length,pos);
    DBUG_RETURN(0);
}


/**
  @brief
  Used to read forward through the index.
  @author:JiangZhaohui
*/

int ha_sysu::index_next(uchar *buf) {  // 第二个开始位置
    byte *key = 0;
    long long pos;
    DBUG_ENTER("ha_sysu::index_next");
    key = share->index_class->get_next_key();
    if(key == 0)
        DBUG_RETURN(HA_ERR_END_OF_FILE);
    pos = share->index_class->get_index_pos((byte *)key,get_key_len());
    share->index_class->seek_index(key,get_key_len());
//    share->index_class->get_next_key();
    if (pos==-1)
        DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
    share->data_class->read_row(buf,table->s->rec_buff_length,pos);
    DBUG_RETURN(0);
}


/**
  @brief
  Used to read backwards through the index.
  @author:JiangZhaohui
*/

int ha_sysu::index_prev(uchar *buf) {
    byte *key=0;
    long long pos;
    DBUG_ENTER("ha_sysu::index_prev");
    key = share->index_class->get_prev_key();
    if(key == 0)
        DBUG_RETURN(HA_ERR_END_OF_FILE);
    pos = share->index_class->get_index_pos((byte *)key, get_key_len());
    share->index_class->seek_index(key,get_key_len());
    share->index_class->get_prev_key();
    if(pos == -1)
        DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
    share->data_class->read_row(buf,table->s->rec_buff_length,pos);
    DBUG_RETURN(0);
}


/**
  @brief
  index_first() asks for the first key in the index.

  @details
  Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc.

  @see
  opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc

  @author:JiangZhaohui
*/
int ha_sysu::index_first(uchar *buf) {  // 第一个key都会从这里开始
    byte *key = 0;
    long long pos = 0;
    DBUG_ENTER("ha_sysu::index_first");
    key = share->index_class->get_first_key();
    if (key == 0)
        DBUG_RETURN(HA_ERR_END_OF_FILE);

    pos = share->index_class->get_index_pos((byte *)key, get_key_len());
    if(pos == -1)
        DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
    share->data_class->read_row(buf,table->s->rec_buff_length,pos);

    DBUG_RETURN(0);
}


/**
  @brief
  index_last() asks for the last key in the index.

  @details
  Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc.

  @see
  opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc

  @author:JiangZhaohui
*/
int ha_sysu::index_last(uchar *buf) {
    byte *key = 0;
    DBUG_ENTER("ha_sysu::index_last");
    key = share->index_class->get_last_key();
    if(key == 0)
        DBUG_RETURN(HA_ERR_END_OF_FILE);
    memcpy(buf, key, get_key_len());
    DBUG_RETURN(0);
}


/**
  @brief
  rnd_init() is called when the system wants the storage engine to do a table
  scan. See the sysu in the introduction at the top of this file to see when
  rnd_init() is called.

  @details
  Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc,
  and sql_update.cc.

  @see
  filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc

  @author: Chenwy
*/
int ha_sysu::rnd_init(bool scan) {
    DBUG_ENTER("ha_sysu::rnd_init");
    current_position = 0;
    stats.records = 0; /* 初始化现在记录数为0 （还没开始扫描）*/
    ref_length = sizeof(long long); /* the length of the current row*/
    DBUG_RETURN(0);
}

int ha_sysu::rnd_end() {
    DBUG_ENTER("ha_sysu::rnd_end");
    DBUG_RETURN(0);
}


/**
  @brief
  This is called for each row of the table scan. When you run out of records
  you should return HA_ERR_END_OF_FILE. Fill buff up with the row information.
  The Field structure for the table is the key to getting data into buf
  in a manner that will allow the server to understand it.

  @details
  Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc,
  and sql_update.cc.

  @see
  filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc

  @author HuangDell
*/
int ha_sysu::rnd_next(uchar *buf) {
    int rc;
    DBUG_ENTER("ha_sysu::rnd_next");
    ha_statistic_increment(&SSV::ha_read_rnd_next_count);  // record read count
    /*  use sysu_data->read_row to read */
    rc = share->data_class->read_row(buf, table->s->rec_buff_length, current_position);
    /* if success rc is 0 else -1  and -1 means end of file*/
    if (rc != -1)
        current_position = (my_off_t) share->data_class->cur_position();
    else
        DBUG_RETURN(HA_ERR_END_OF_FILE);
    stats.records++;
    DBUG_RETURN(0);
}


/**
  @brief
  position() is called after each call to rnd_next() if the data needs
  to be ordered. You can do something like the following to store
  the position:
  @code
  my_store_ptr(ref, ref_length, current_position);
  @endcode

  @details
  The server uses ref to store data. ref_length in the above case is
  the size needed to store current_position. ref is just a byte array
  that the server will maintain. If you are using offsets to mark rows, then
  current_position should be the offset. If it is a primary key like in
  BDB, then it needs to be a primary key.

  Called from filesort.cc, sql_select.cc, sql_delete.cc, and sql_update.cc.

  @see
  filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc

  @author HuangDell
  保存当前文件指针 current_position
*/
void ha_sysu::position(const uchar *record) {
    DBUG_ENTER("ha_sysu::position");
    /* use my_store_pr to save ptr which point at current file read/write position */
    my_store_ptr(ref, ref_length, current_position);
    DBUG_VOID_RETURN;
}


/**
  @brief
  This is like rnd_next, but you are given a position to use
  to determine the row. The position will be of the type that you stored in
  ref. You can use ha_get_ptr(pos,ref_length) to retrieve whatever key
  or position you saved when position() was called.

  @details
  Called from filesort.cc, records.cc, sql_insert.cc, sql_select.cc, and sql_update.cc.

  @see
  filesort.cc, records.cc, sql_insert.cc, sql_select.cc and sql_update.cc

  @author HuangDell
*/
int ha_sysu::rnd_pos(uchar *buf, uchar *pos) {
    DBUG_ENTER("ha_sysu::rnd_pos");
    ha_statistic_increment(&SSV::ha_read_rnd_next_count);
    current_position = (my_off_t) my_get_ptr(pos, ref_length);
    share->data_class->read_row(buf, current_position, -1);
    DBUG_RETURN(0);
}


/**
  @brief
  ::info() is used to return information to the optimizer. See my_base.h for
  the complete description.

  @details
  Currently this table handler doesn't implement most of the fields really needed.
  SHOW also makes use of this data.

  You will probably want to have the following in your code:
  @code
  if (records < 2)
    records = 2;
  @endcode
  The reason is that the server will optimize for cases of only a single
  record. If, in a table scan, you don't know the number of records, it
  will probably be better to set records to two so you can return as many
  records as you need. Along with records, a few more variables you may wish
  to set are:
    records
    deleted
    data_file_length
    index_file_length
    delete_length
    check_time
  Take a look at the public variables in handler.h for more information.

  Called in filesort.cc, ha_heap.cc, item_sum.cc, opt_sum.cc, sql_delete.cc,
  sql_delete.cc, sql_derived.cc, sql_select.cc, sql_select.cc, sql_select.cc,
  sql_select.cc, sql_select.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_show.cc,
  sql_table.cc, sql_union.cc, and sql_update.cc.

  @see
  filesort.cc, ha_heap.cc, item_sum.cc, opt_sum.cc, sql_delete.cc, sql_delete.cc,
  sql_derived.cc, sql_select.cc, sql_select.cc, sql_select.cc, sql_select.cc,
  sql_select.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_table.cc,
  sql_union.cc and sql_update.cc

  @author HuangDell
*/
int ha_sysu::info(uint flag) {
    DBUG_ENTER("ha_sysu::info");
    if (stats.records < 2)
        stats.records = 2;
    DBUG_RETURN(0);
}


/**
  @brief
  extra() is called whenever the server wishes to send a hint to
  the storage engine. The myisam engine implements the most hints.
  ha_innodb.cc has the most exhaustive list of these hints.

    @see
  ha_innodb.cc
*/
int ha_sysu::extra(enum ha_extra_function operation) {
    DBUG_ENTER("ha_sysu::extra");
    DBUG_RETURN(0);
}


/**
  @brief
  Used to delete all rows in a table, including cases of truncate and cases where
  the optimizer realizes that all rows will be removed as a result of an SQL statement.

  @details
  Called from item_sum.cc by Item_func_group_concat::clear(),
  Item_sum_count_distinct::clear(), and Item_func_group_concat::clear().
  Called from sql_delete.cc by mysql_delete().
  Called from sql_select.cc by JOIN::reinit().
  Called from sql_union.cc by st_select_lex_unit::exec().

  @see
  Item_func_group_concat::clear(), Item_sum_count_distinct::clear() and
  Item_func_group_concat::clear() in item_sum.cc;
  mysql_delete() in sql_delete.cc;
  JOIN::reinit() in sql_select.cc and
  st_select_lex_unit::exec() in sql_union.cc.

  @author HuangDell
*/
int ha_sysu::delete_all_rows() {
    DBUG_ENTER("ha_sysu::delete_all_rows");
    mysql_mutex_lock(&sysu_mutex);
    share->data_class->trunc_table();
    share->index_class->destroy_index();
    share->index_class->trunc_index();
    mysql_mutex_unlock(&sysu_mutex);
    DBUG_RETURN(0);
}


/**
  @brief
  Used for handler specific truncate table.  The table is locked in
  exclusive mode and handler is responsible for reseting the auto-
  increment counter.

  @details
  Called from Truncate_statement::handler_truncate.
  Not used if the handlerton supports HTON_CAN_RECREATE, unless this
  engine can be used as a partition. In this case, it is invoked when
  a particular partition is to be truncated.

  @see
  Truncate_statement in sql_truncate.cc
  Remarks in handler::truncate.
*/
int ha_sysu::truncate() {
    DBUG_ENTER("ha_sysu::truncate");
    DBUG_RETURN(HA_ERR_WRONG_COMMAND);
}


/**
  @brief
  This create a lock on the table. If you are implementing a storage engine
  that can handle transacations look at ha_berkely.cc to see how you will
  want to go about doing this. Otherwise you should consider calling flock()
  here. Hint: Read the section "locking functions for mysql" in lock.cc to understand
  this.

  @details
  Called from lock.cc by lock_external() and unlock_external(). Also called
  from sql_table.cc by copy_data_between_tables().

  @see
  lock.cc by lock_external() and unlock_external() in lock.cc;
  the section "locking functions for mysql" in lock.cc;
  copy_data_between_tables() in sql_table.cc.
*/
int ha_sysu::external_lock(THD *thd, int lock_type) {
    DBUG_ENTER("ha_sysu::external_lock");
    DBUG_RETURN(0);
}


/**
  @brief
  The idea with handler::store_lock() is: The statement decides which locks
  should be needed for the table. For updates/deletes/inserts we get WRITE
  locks, for SELECT... we get read locks.

  @details
  Before adding the lock into the table lock handler (see thr_lock.c),
  mysqld calls store lock with the requested locks. Store lock can now
  modify a write lock to a read lock (or some other lock), ignore the
  lock (if we don't want to use MySQL table locks at all), or add locks
  for many tables (like we do when we are using a MERGE handler).

  Berkeley DB, for sysu, changes all WRITE locks to TL_WRITE_ALLOW_WRITE
  (which signals that we are doing WRITES, but are still allowing other
  readers and writers).

  When releasing locks, store_lock() is also called. In this case one
  usually doesn't have to do anything.

  In some exceptional cases MySQL may send a request for a TL_IGNORE;
  This means that we are requesting the same lock as last time and this
  should also be ignored. (This may happen when someone does a flush
  table when we have opened a part of the tables, in which case mysqld
  closes and reopens the tables and tries to get the same locks at last
  time). In the future we will probably try to remove this.

  Called from lock.cc by get_lock_data().

  @note
  In this method one should NEVER rely on table->in_use, it may, in fact,
  refer to a different thread! (this happens if get_lock_data() is called
  from mysql_lock_abort_for_thread() function)

  @see
  get_lock_data() in lock.cc
*/
THR_LOCK_DATA **ha_sysu::store_lock(THD *thd,
                                    THR_LOCK_DATA **to,
                                    enum thr_lock_type lock_type) {
    if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
        lock.type = lock_type;
    *to++ = &lock;
    return to;
}


/**
  @brief
  Used to delete a table. By the time delete_table() has been called all
  opened references to this table will have been closed (and your globally
  shared references released). The variable name will just be the name of
  the table. You will need to remove any files you have created at this point.

  @details
  If you do not implement this, the default delete_table() is called from
  handler.cc and it will delete all files with the file extensions returned
  by bas_ext().

  Called from handler.cc by delete_table and ha_create_table(). Only used
  during create if the table_flag HA_DROP_BEFORE_CREATE was specified for
  the storage engine.

  @see
  delete_table and ha_create_table() in handler.cc
*/
int ha_sysu::delete_table(const char *name) {
    DBUG_ENTER("ha_sysu::delete_table");
    /* This is not implemented but we want someone to be able that it works. */
    char name_buff[FN_REFLEN];
    /*
     Begin critical section by locking the sysu mutex variable.
    */
    if (!(share = get_share(name, table)))
        DBUG_RETURN(1);
    mysql_mutex_lock(&sysu_mutex);
    share->data_class->close_table();
    share->index_class->destroy_index();
    share->index_class->close_index();
    /*
     Call the mysql delete file method.
     Note: the fn_format() method correctly creates a file name from the
     name passed into the method.
    */
    my_delete(fn_format(name_buff, name, "", SDE_EXT, MY_REPLACE_EXT | MY_UNPACK_FILENAME), MYF(0));
    /* delete index file */
    my_delete(fn_format(name_buff, name, "", SDI_EXT, MY_REPLACE_EXT | MY_UNPACK_FILENAME), MYF(0));
    /*
     End section by unlocking the sysu mutex variable.
    */
    mysql_mutex_unlock(&sysu_mutex);
    DBUG_RETURN(0);
}


/**
  @brief
  Renames a table from one name to another via an alter table call.

  @details
  If you do not implement this, the default rename_table() is called from
  handler.cc and it will delete all files with the file extensions returned
  by bas_ext().

  Called from sql_table.cc by mysql_rename_table().

  @see
  mysql_rename_table() in sql_table.cc

  @author HuangDell
*/
int ha_sysu::rename_table(const char *from, const char *to) {
    DBUG_ENTER("ha_sysu::rename_table ");
    char data_from[FN_REFLEN];
    char data_to[FN_REFLEN];
    char index_from[FN_REFLEN];
    char index_to[FN_REFLEN];

    if (!(share = get_share(from, table)))
        DBUG_RETURN(1);
    /*
      Begin critical section by locking the sysu mutex variable.
    */
    mysql_mutex_lock(&sysu_mutex);
    /*
      Close the table then copy it then reopen new file.
    */
    share->data_class->close_table();
    my_copy(fn_format(data_from, from, "", SDE_EXT,
                      MY_REPLACE_EXT | MY_UNPACK_FILENAME),
            fn_format(data_to, to, "", SDE_EXT,
                      MY_REPLACE_EXT | MY_UNPACK_FILENAME), MYF(0));
    share->data_class->open_table(data_to);
    share->index_class->close_index();
    my_copy(fn_format(index_from,from, "",SDI_EXT,
            MY_REPLACE_EXT |MY_UNPACK_FILENAME),fn_format(index_to,to,"",SDI_EXT,
            MY_REPLACE_EXT|MY_UNPACK_FILENAME),MYF(0));
    share->index_class->open_index(index_to);

    /*
      End section by unlocking the sysu mutex variable.
    */
    mysql_mutex_unlock(&sysu_mutex);
    /*
      Delete the file using MySQL's delete file method.
    */
    my_delete(data_from, MYF(0));
    my_delete(index_from, MYF(0));

    DBUG_RETURN(0);
}


/**
  @brief
  Given a starting key and an ending key, estimate the number of rows that
  will exist between the two keys.

  @details
  end_key may be empty, in which case determine if start_key matches any rows.

  Called from opt_range.cc by check_quick_keys().

  @see
  check_quick_keys() in opt_range.cc
*/
ha_rows ha_sysu::records_in_range(uint inx, key_range *min_key,
                                  key_range *max_key) {
    DBUG_ENTER("ha_sysu::records_in_range");
    DBUG_RETURN(10);                         // low number to force index usage
}


/**
  @brief
  create() is called to create a database. The variable name will have the name
  of the table.

  @details
  When create() is called you do not need to worry about
  opening the table. Also, the .frm file will have already been
  created so adjusting create_info is not necessary. You can overwrite
  the .frm file at this point if you wish to change the table
  definition, but there are no methods currently provided for doing
  so.

  Called from handle.cc by ha_create_table().

  @see
  ha_create_table() in handle.cc
  创建一个空白文件来容纳表里的数据，找到create()方法，把用来获得share结构一份副本的代码添加到其中，
  调用data_class对象的create_ _table()方法，最后关闭表。
  @autor:Hlx
*/
int ha_sysu::create(const char *name, TABLE *table_arg,
                    HA_CREATE_INFO *create_info) {
    DBUG_ENTER("ha_sysu::create");
    char name_buff[FN_REFLEN];  /*create a name buffer with max length of full path-name */
    if (!(share = get_share(name, table)))  // get a share for lock controls
        DBUG_RETURN(1);
    // fn_format function just for creating a file name, so why not use concat? :)
    if (share->data_class->create_table(fn_format(name_buff, name, "", SDE_EXT,
                                                  MY_REPLACE_EXT | MY_UNPACK_FILENAME)))
        DBUG_RETURN(-1);

    if (share->index_class->create_index(fn_format(name_buff,name,"",SDI_EXT,
                                                   MY_REPLACE_EXT|MY_UNPACK_FILENAME), 128))
        DBUG_RETURN(-1);

    share->index_class->close_index();
    share->data_class->close_table();
    DBUG_RETURN(0);
}

/**
 * search key from a table
 * @return key values if found else 0
 * @author HuangDell
 */
byte *ha_sysu::get_key() {
    byte *key = 0;
    DBUG_ENTER("ha_sysu::get_key");
    /* check each field in the table to see if it is the key */
    for (Field **field = table->field; *field; field++) {
        if ((*field)->key_start.to_ulonglong() == 1)  // key_start =1 means is a key
        {
            int length = (*field)->field_length;
            key = (byte *) my_malloc(sysu_key_memory_index, length, MYF(MY_ZEROFILL | MY_WME));
            memcpy(key, (*field)->ptr, (*field)->key_length());
        }
    }
    DBUG_RETURN(key);
}


/**
 * @return length if found key else 0
 * @author HuangDell
 */
int ha_sysu::get_key_len() {
    int length = 0;
    DBUG_ENTER("ha_spartan::get_key");
    /* check each field in the table to see if it is the key */
    for (Field **field = table->field; *field; field++) {
        if ((*field)->key_start.to_ulonglong() == 1) {
            length = (*field)->key_length();
            DBUG_RETURN(length);
        }
    }
    DBUG_RETURN(0);
}

struct st_mysql_storage_engine sysu_storage_engine =
        {MYSQL_HANDLERTON_INTERFACE_VERSION};

static ulong srv_enum_var = 0;
static ulong srv_ulong_var = 0;
static double srv_double_var = 0;

const char *enum_var_names[] =
        {
                "e1", "e2", NullS
        };

TYPELIB enum_var_typelib =
        {
                array_elements(enum_var_names) - 1, "enum_var_typelib",
                enum_var_names, NULL
        };


static MYSQL_SYSVAR_ENUM(
        enum_var,                       // name
        srv_enum_var,                   // varname
        PLUGIN_VAR_RQCMDARG,            // opt
        "Sample ENUM system variable.", // comment
        NULL,                           // check
        NULL,                           // update
        0,                              // def
        &enum_var_typelib);             // typelib

static MYSQL_SYSVAR_ULONG(
        ulong_var,
        srv_ulong_var,
        PLUGIN_VAR_RQCMDARG,
        "0..1000",
        NULL,
        NULL,
        8,
        0,
        1000,
        0);

static MYSQL_SYSVAR_DOUBLE(
        double_var,
        srv_double_var,
        PLUGIN_VAR_RQCMDARG,
        "0.500000..1000.500000",
        NULL,
        NULL,
        8.5,
        0.5,
        1000.5,
        0);                             // reserved always 0

static MYSQL_THDVAR_DOUBLE(
        double_thdvar,
        PLUGIN_VAR_RQCMDARG,
        "0.500000..1000.500000",
        NULL,
        NULL,
        8.5,
        0.5,
        1000.5,
        0);

static struct st_mysql_sys_var *sysu_system_variables[] = {
        MYSQL_SYSVAR(enum_var),
        MYSQL_SYSVAR(ulong_var),
        MYSQL_SYSVAR(double_var),
        MYSQL_SYSVAR(double_thdvar),
        NULL
};

// this is a sysu of SHOW_FUNC and of my_snprintf() service
static int show_func_sysu(MYSQL_THD thd, struct st_mysql_show_var *var,
                          char *buf) {
    var->type = SHOW_CHAR;
    var->value = buf; // it's of SHOW_VAR_FUNC_BUFF_SIZE bytes
    my_snprintf(buf, SHOW_VAR_FUNC_BUFF_SIZE,
                "enum_var is %lu, ulong_var is %lu, "
                "double_var is %f, %.6b", // %b is a MySQL extension
                srv_enum_var, srv_ulong_var, srv_double_var, "really");
    return 0;
}

struct sysu_vars_t {
    ulong var1;
    double var2;
    char var3[64];
    bool var4;
    bool var5;
    ulong var6;
};

sysu_vars_t sysu_vars = {100, 20.01, "three hundred", true, 0, 8250};

static st_mysql_show_var show_status_sysu[] =
        {
                {"var1", (char *) &sysu_vars.var1, SHOW_LONG,   SHOW_SCOPE_GLOBAL},
                {"var2", (char *) &sysu_vars.var2, SHOW_DOUBLE, SHOW_SCOPE_GLOBAL},
                {0,      0,                        SHOW_UNDEF,  SHOW_SCOPE_UNDEF} // null terminator required
        };

static struct st_mysql_show_var show_array_sysu[] =
        {
                {"array", (char *) show_status_sysu, SHOW_ARRAY, SHOW_SCOPE_GLOBAL},
                {"var3",  (char *) &sysu_vars.var3,  SHOW_CHAR,  SHOW_SCOPE_GLOBAL},
                {"var4",  (char *) &sysu_vars.var4,  SHOW_BOOL,  SHOW_SCOPE_GLOBAL},
                {0,       0,                         SHOW_UNDEF, SHOW_SCOPE_UNDEF}
        };

static struct st_mysql_show_var func_status[] =
        {
                {"sysu_func_sysu",   (char *) show_func_sysu,  SHOW_FUNC,  SHOW_SCOPE_GLOBAL},
                {"sysu_status_var5", (char *) &sysu_vars.var5, SHOW_BOOL,  SHOW_SCOPE_GLOBAL},
                {"sysu_status_var6", (char *) &sysu_vars.var6, SHOW_LONG,  SHOW_SCOPE_GLOBAL},
                {"sysu_status",      (char *) show_array_sysu, SHOW_ARRAY, SHOW_SCOPE_GLOBAL},
                {0,                  0,                        SHOW_UNDEF, SHOW_SCOPE_UNDEF}
        };

mysql_declare_plugin(sysu)
                {
                        MYSQL_STORAGE_ENGINE_PLUGIN,
                        &sysu_storage_engine,
                        "sysu",
                        "Brian Aker, MySQL AB",
                        "sysu storage engine",
                        PLUGIN_LICENSE_GPL,
                        sysu_init_func,                            /* Plugin Init */
                        NULL,                                         /* Plugin Deinit */
                        0x0001 /* 0.1 */,
                        func_status,                                  /* status variables */
                        sysu_system_variables,                     /* system variables */
                        NULL,                                         /* config options */
                        0,                                            /* flags */
                }mysql_declare_plugin_end;
